/* Copyright (c) 2015, 2021, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */

// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include "my_decimal.h"
#include "sql_string.h"
#include "sql_time.h"
#include "json_binary.h"
#include "json_dom.h"
#include "base64.h"
#include "template_utils.h"     // down_cast

#include <gtest/gtest.h>
#include "test_utils.h"

#include <memory>
#include <cstring>

/**
 Test Json_dom class hierarchy API, cf. json_dom.h
 */
namespace json_dom_unittest {

class JsonDomTest : public ::testing::Test
{
protected:
  virtual void SetUp() { initializer.SetUp(); }
  virtual void TearDown() { initializer.TearDown(); }
  my_testing::Server_initializer initializer;
};

/**
   Format a Json_dom object to JSON text using  Json_wrapper's
   to_string functionality.

   @param d The DOM object to be formatted
*/
std::string format(const Json_dom &d)
{
  String buffer;
  Json_wrapper w(d.clone());
  EXPECT_FALSE(w.to_string(&buffer, true, "format"));

  return std::string(buffer.ptr(), buffer.length());
}

std::string format(const Json_dom *ptr)
{
  return format(*ptr);
}

TEST_F(JsonDomTest, BasicTest)
{
  String buffer;
  /* string scalar */
  const std::string std_s("abc");
  Json_string s(std_s);
  EXPECT_EQ(std_s, s.value());
  EXPECT_EQ(Json_dom::J_STRING, s.json_type());
  EXPECT_TRUE(s.is_scalar());
  EXPECT_EQ(1U, s.depth());
  EXPECT_FALSE(s.is_number());
  EXPECT_EQ(std::string("\"abc\""), format(s));

  /*
    Escaping in strings, cf. ECMA-404 The JSON Data Interchange Format
  */
  Json_array a;
  /* double quote and backslash */
  Json_string js1(std::string("a\"b\\c"));
  a.append_clone(&js1);
  EXPECT_EQ(std::string("[\"a\\\"b\\\\c\"]"), format(a));

  a.clear();
  /* Printable control characters */
  Json_string js2(std::string("a\b\f\n\r\tb"));
  a.append_clone(&js2);
  EXPECT_EQ(7U, static_cast<Json_string *>(a[0])->size());
  EXPECT_EQ(std::string("[\"a\\b\\f\\n\\r\\tb\"]"), format(a));

  a.clear();
  /* Unprintable control characters and non-ASCII Unicode characters */
  Json_string js3(std::string("丳\x13" "丽\x3"));
  a.append_clone(&js3);
  EXPECT_EQ(std::string("[\"丳\\u0013丽\\u0003\"]"), format(a));

  /* boolean scalar */
  const Json_boolean jb(true);
  EXPECT_EQ(Json_dom::J_BOOLEAN, jb.json_type());
  EXPECT_EQ(true, jb.value());
  EXPECT_EQ(std::string("true"), format(jb));

  /* Integer scalar */
  const Json_int ji(-123);
  EXPECT_EQ(Json_dom::J_INT, ji.json_type());
  EXPECT_EQ(-123, ji.value());
  EXPECT_EQ(std::string("-123"), format(ji));

  const Json_int max_32_int(2147483647);
  EXPECT_EQ(std::string("2147483647"), format(max_32_int));

  const Json_int max_64_int(9223372036854775807LL);
  EXPECT_EQ(std::string("9223372036854775807"), format(max_64_int));

  const Json_uint max_64_uint(18446744073709551615ULL);
  EXPECT_EQ(Json_dom::J_UINT, max_64_uint.json_type());
  EXPECT_EQ(std::string("18446744073709551615"), format(max_64_uint));

  /* Double scalar */
  const Json_double jdb(-123.45);
  EXPECT_EQ(Json_dom::J_DOUBLE, jdb.json_type());
  EXPECT_EQ(-123.45, jdb.value());
  EXPECT_EQ(std::string("-123.45"), format(jdb));

  /* Simple array with strings */
  a.clear();
  EXPECT_EQ(Json_dom::J_ARRAY, a.json_type());
  EXPECT_FALSE(a.is_scalar());
  EXPECT_EQ(0U, a.size());
  Json_string js4(std::string("val1"));
  a.append_clone(&js4);
  Json_string js5(std::string("val2"));
  a.append_clone(&js5);
  EXPECT_EQ(2U, a.size());
  EXPECT_EQ(std::string("[\"val1\", \"val2\"]"), format(a));
  EXPECT_EQ(2U, a.depth());
  Json_dom *elt0= a[0];
  Json_dom *elt1= a[a.size() - 1];
  EXPECT_EQ(std::string("\"val1\""), format(elt0));
  EXPECT_EQ(std::string("\"val2\""), format(elt1));

  /* Simple object with string values, iterator and array cloning */
  Json_object o;
  EXPECT_EQ(Json_dom::J_OBJECT, o.json_type());
  EXPECT_FALSE(a.is_scalar());
  EXPECT_EQ(0U, o.cardinality());
  Json_null null;
  EXPECT_EQ(Json_dom::J_NULL, null.json_type());
  o.add_clone(std::string("key1"), &null);
  o.add_clone(std::string("key2"), &a);

  const std::string key_expected[]=
    {std::string("key1"), std::string("key2")};
  const std::string value_expected[]=
    {std::string("null"), std::string("[\"val1\", \"val2\"]")};

  int idx= 0;

  for (Json_object::const_iterator i= o.begin(); i != o.end(); ++i)
  {
    EXPECT_EQ(key_expected[idx], i->first);
    EXPECT_EQ(value_expected[idx], format(i->second));
    idx++;
  }

  /* Test uniqueness of keys */
  Json_string js6(std::string("should be discarded"));
  o.add_clone(std::string("key1"), &js6);
  EXPECT_EQ(2U, o.cardinality());
  EXPECT_EQ(std::string("{\"key1\": null, \"key2\": [\"val1\", \"val2\"]}"),
            format(o));
  EXPECT_EQ(3U, o.depth());

  /* Nested array inside object and object inside array,
   * and object cloning
   */
  Json_array level3;
  level3.append_clone(&o);
  Json_int ji2(123);
  level3.insert_clone(0U, &ji2);
  EXPECT_EQ(std::string("[123, {\"key1\": null, \"key2\": "
                        "[\"val1\", \"val2\"]}]"),
            format(level3));
  EXPECT_EQ(4U, level3.depth());

  /* Array access: index */
  Json_dom * const elt= level3[1];
  EXPECT_EQ(std::string("{\"key1\": null, \"key2\": "
                        "[\"val1\", \"val2\"]}"),
            format(elt));

  /* Object access: key look-up */
  EXPECT_EQ(Json_dom::J_OBJECT, elt->json_type());
  Json_object * const object_elt= down_cast<Json_object *>(elt);
  EXPECT_TRUE(object_elt != NULL);
  const Json_dom * const elt2= object_elt->get(std::string("key1"));
  EXPECT_EQ(std::string("null"), format(elt2));

  /* Clear object. */
  object_elt->clear();
  EXPECT_EQ(0U, object_elt->cardinality());

  /* Array remove element */
  EXPECT_TRUE(level3.remove(1));
  EXPECT_EQ(std::string("[123]"), format(level3));
  EXPECT_FALSE(level3.remove(level3.size()));
  EXPECT_EQ(std::string("[123]"), format(level3));

  /* Decimal scalar, including cloning */
  my_decimal m;
  EXPECT_FALSE(double2my_decimal(0, 3.14, &m));

  const Json_decimal jd(m);
  EXPECT_EQ(Json_dom::J_DECIMAL, jd.json_type());
  EXPECT_TRUE(jd.is_number());
  EXPECT_TRUE(jd.is_scalar());
  const my_decimal m_out= *jd.value();
  double m_d;
  double m_out_d;

  decimal2double(&m, &m_d);
  decimal2double(&m_out, &m_out_d);
  EXPECT_EQ(m_d, m_out_d);

  a.append_clone(&jd);
  std::auto_ptr<Json_array> b(static_cast<Json_array *>(a.clone()));
  EXPECT_EQ(std::string("[\"val1\", \"val2\", 3.14]"), format(a));
  EXPECT_EQ(std::string("[\"val1\", \"val2\", 3.14]"), format(b.get()));

  /* Array insert beyond end appends at end */
  a.clear();
  a.insert_alias(0, new (std::nothrow) Json_int(0));
  a.insert_alias(2, new (std::nothrow) Json_int(2));
  EXPECT_EQ(std::string("[0, 2]"), format(a));
  a.clear();
  a.insert_alias(0, new (std::nothrow) Json_int(0));
  a.insert_alias(1, new (std::nothrow) Json_int(1));
  EXPECT_EQ(std::string("[0, 1]"), format(a));

  /* Array clear, null type, boolean literals, including cloning */
  a.clear();
  Json_null jn;
  Json_boolean jbf(false);
  Json_boolean jbt(true);
  a.append_clone(&jn);
  a.append_clone(&jbf);
  a.append_clone(&jbt);
  std::auto_ptr<const Json_dom> c(a.clone());
  EXPECT_EQ(std::string("[null, false, true]"), format(a));
  EXPECT_EQ(std::string("[null, false, true]"), format(c.get()));
}

TEST_F(JsonDomTest, BasicTestTwo)
{
  Json_array a;
  /* DATETIME scalar */
  MYSQL_TIME dt;
  std::memset(&dt, 0, sizeof dt);
  MYSQL_TIME_STATUS status;
  EXPECT_FALSE(str_to_datetime(&my_charset_utf8mb4_bin,
                               "19990412",
                               8,
                               &dt,
                               (my_time_flags_t)0,
                               &status));
  const Json_datetime scalar(dt, MYSQL_TYPE_DATETIME);
  EXPECT_EQ(Json_dom::J_DATETIME, scalar.json_type());

  const MYSQL_TIME *dt_out= scalar.value();

  EXPECT_FALSE(std::memcmp(&dt, dt_out, sizeof(MYSQL_TIME)));
  EXPECT_EQ(std::string("\"1999-04-12\""), format(scalar));

  a.clear();
  a.append_clone(&scalar);
  EXPECT_EQ(std::string("[\"1999-04-12\"]"), format(a));

  EXPECT_FALSE(str_to_datetime(&my_charset_utf8mb4_bin,
                               "14-11-15 12.04.55.123456",
                               24,
                               &dt,
                               (my_time_flags_t)0,
                               &status));

  Json_datetime scalar2(dt, MYSQL_TYPE_DATETIME);
  EXPECT_EQ(std::string("\"2014-11-15 12:04:55.123456\""), format(scalar2));

  /* Opaque type storage scalar */
  const uint32 i= 0xCAFEBABE;
  char i_as_char[4];
  int4store(i_as_char, i);
  Json_opaque opaque(MYSQL_TYPE_TINY_BLOB, i_as_char, sizeof(i_as_char));
  EXPECT_EQ(Json_dom::J_OPAQUE, opaque.json_type());
  EXPECT_EQ(i, uint4korr(opaque.value()));
  EXPECT_EQ(MYSQL_TYPE_TINY_BLOB, opaque.type());
  EXPECT_EQ(sizeof(i_as_char), opaque.size());
  EXPECT_EQ(std::string("\"base64:type249:vrr+yg==\""),
            format(opaque));

  const char *encoded= "vrr+yg==";
  char *buff= new char[static_cast<size_t>
                       (base64_needed_decoded_length(static_cast<int>
                                                     (std::strlen(encoded))))];
  EXPECT_EQ(4, base64_decode(encoded, std::strlen(encoded), buff, NULL, 0));
  EXPECT_EQ(0xCAFEBABE, uint4korr(buff));
  delete[] buff;

  /* Build DOM from JSON text using rapdjson */
  const char *msg;
  size_t msg_offset;
  const char *sample_doc=
    "{\"abc\": 3, \"foo\": [1, 2, {\"foo\": 3.24}, null]}";
  std::auto_ptr<Json_dom> dom(Json_dom::parse(sample_doc,
                                              std::strlen(sample_doc),
                                              &msg, &msg_offset));
  EXPECT_TRUE(dom.get() != NULL);
  EXPECT_EQ(4U, dom->depth());
  EXPECT_EQ(std::string(sample_doc), format(dom.get()));

  const char *sample_array=
    "[3, {\"abc\": \"\\u0000inTheText\"}]";
  dom.reset(Json_dom::parse(sample_array, std::strlen(sample_array),
                            &msg, &msg_offset));
  EXPECT_TRUE(dom.get() != NULL);
  EXPECT_EQ(3U, dom->depth());
  EXPECT_EQ(std::string(sample_array), format(dom.get()));

  const char *sample_scalar_doc= "2";
  dom.reset(Json_dom::parse(sample_scalar_doc, std::strlen(sample_scalar_doc),
                            &msg, &msg_offset));
  EXPECT_TRUE(dom.get() != NULL);
  EXPECT_EQ(std::string(sample_scalar_doc), format(dom.get()));

  const char *max_uint_scalar= "18446744073709551615";
  dom.reset(Json_dom::parse(max_uint_scalar, std::strlen(max_uint_scalar),
                            &msg, &msg_offset));
  EXPECT_EQ(std::string(max_uint_scalar), format(dom.get()));

  /*
    Test that duplicate keys are eliminated, and that the returned
    keys are in the expected order (sorted on length before
    contents).
  */
  const char *sample_object= "{\"key1\":1, \"key2\":2, \"key1\":3, "
    "\"key1\\u0000x\":4, \"key1\\u0000y\":5, \"a\":6, \"ab\":7, \"b\":8, "
    "\"\":9, \"\":10}";
  const std::string expected[8][2]=
    {
      { "",        "9" },
      { "a",       "6" },
      { "b",       "8" },
      { "ab",      "7" },
      { "key1",    "1" },
      { "key2",    "2" },
      { std::string("key1\0x", 6), "4" },
      { std::string("key1\0y", 6), "5" },
    };
  dom.reset(Json_dom::parse(sample_object, std::strlen(sample_object),
                            &msg, &msg_offset));
  EXPECT_TRUE(dom.get() != NULL);
  const Json_object *obj= down_cast<const Json_object *>(dom.get());
  EXPECT_EQ(8U, obj->cardinality());
  int idx= 0;

  for (Json_object::const_iterator it= obj->begin(); it != obj->end(); ++it)
  {
    EXPECT_EQ(expected[idx][0], it->first);
    EXPECT_EQ(expected[idx][1], format(it->second));
    idx++;
  }

  EXPECT_EQ(8, idx);

  /* Try to build DOM for JSON text using rapidjson on invalid text
     Included so we test error recovery
  */
  const char *half_object_item= "{\"label\": ";
  dom.reset(Json_dom::parse(half_object_item, std::strlen(half_object_item),
                            &msg, &msg_offset));
  const Json_dom *null_dom= NULL;
  EXPECT_EQ(null_dom, dom.get());

  const char *half_array_item= "[1,";
  dom.reset(Json_dom::parse(half_array_item, std::strlen(half_array_item),
                            &msg, &msg_offset));
  EXPECT_EQ(null_dom, dom.get());
}

/*
  Test that special characters are escaped when a Json_string is
  converted to text, so that it is possible to parse the resulting
  string. The JSON parser requires all characters in the range [0x00,
  0x1F] and the characters " (double-quote) and \ (backslash) to be
  escaped.
*/
TEST_F(JsonDomTest, EscapeSpecialChars)
{
  // Create a JSON string with all characters in the range [0, 127].
  char input[128];
  for (size_t i= 0; i < sizeof(input); ++i)
    input[i]= static_cast<char>(i);
  const Json_string jstr(std::string(input, sizeof(input)));

  // Now convert that value from JSON to text and back to JSON.
  std::string str= format(jstr);
  std::auto_ptr<Json_dom> dom(Json_dom::parse(str.c_str(), str.length(),
                                              NULL, NULL));
  EXPECT_NE(static_cast<Json_dom*>(NULL), dom.get());
  EXPECT_EQ(Json_dom::J_STRING, dom->json_type());

  // Expect to get the same string back, including all the special characters.
  const Json_string *jstr2= down_cast<const Json_string *>(dom.get());
  EXPECT_EQ(jstr.value(), jstr2->value());
}

void vet_wrapper_length(char * text, size_t expected_length )
{
  const char *msg;
  size_t msg_offset;
  Json_dom *dom= Json_dom::parse(text, std::strlen(text), &msg, &msg_offset);
  Json_wrapper dom_wrapper(dom);

  EXPECT_EQ(expected_length, dom_wrapper.length())
    << "Wrapped DOM: " << text << "\n";

  String  serialized_form;
  EXPECT_FALSE(json_binary::serialize(dom, &serialized_form));
  json_binary::Value binary=
    json_binary::parse_binary(serialized_form.ptr(),
                              serialized_form.length());
  Json_wrapper  binary_wrapper(binary);

  json_binary::Value::enum_type  binary_type= binary.type();

  if ((binary_type == json_binary::Value::ARRAY) ||
      (binary_type == json_binary::Value::OBJECT))
  {
    EXPECT_EQ(expected_length, binary.element_count())
      << "BINARY: " << text << " and data = " << binary.get_data() << "\n";
  }
  EXPECT_EQ(expected_length, binary_wrapper.length())
    << "Wrapped BINARY: " << text << "\n";
}

TEST_F(JsonDomTest, WrapperTest)
{
  // Constructors, assignment, copy constructors, aliasing
  Json_dom *d= new (std::nothrow) Json_null();
  Json_wrapper w(d);
  EXPECT_EQ(w.to_dom(), d);
  Json_wrapper w_2(w);
  EXPECT_NE(w.to_dom(), w_2.to_dom()); // deep copy

  Json_wrapper w_2b;
  EXPECT_TRUE(w_2b.empty());
  w_2b= w;
  EXPECT_NE(w.to_dom(), w_2b.to_dom()); // deep copy

  w.set_alias(); // d is now "free" again
  Json_wrapper w_3(w);
  EXPECT_EQ(w.to_dom(), w_3.to_dom()); // alias copy
  w_3= w;
  EXPECT_EQ(w.to_dom(), w_3.to_dom()); // alias copy

  Json_wrapper w_4(d); // give d a new owner
  Json_wrapper w_5;
  w_5.steal(&w_4); // takes over d
  EXPECT_EQ(w_4.to_dom(), w_5.to_dom());

  Json_wrapper w_6;
  EXPECT_EQ(Json_dom::J_ERROR, w_6.type());
  EXPECT_EQ(0U, w_6.length());
  EXPECT_EQ(0U, w_6.depth());

  Json_dom *i= new (std::nothrow) Json_int(1);
  Json_wrapper w_7(i);
  w_5.steal(&w_7); // should deallocate w_5's original

  // scalars
  vet_wrapper_length((char *) "false", 1);
  vet_wrapper_length((char *) "true", 1);
  vet_wrapper_length((char *) "null", 1);
  vet_wrapper_length((char *) "1.1", 1);
  vet_wrapper_length((char *) "\"hello world\"", 1);

  // objects
  vet_wrapper_length((char *) "{}", 0);
  vet_wrapper_length((char *) "{ \"a\" : 100 }", 1);
  vet_wrapper_length((char *) "{ \"a\" : 100, \"b\" : 200 }", 2);

  // arrays
  vet_wrapper_length((char *) "[]", 0);
  vet_wrapper_length((char *) "[ 100 ]", 1);
  vet_wrapper_length((char *) "[ 100, 200 ]", 2);

  // nested objects
  vet_wrapper_length((char *) "{ \"a\" : 100, \"b\" : { \"c\" : 300 } }", 2);

  // nested arrays
  vet_wrapper_length((char *) "[ 100, [ 200, 300 ] ]", 2);
}

void vet_merge(char * left_text, char * right_text, std::string expected )
{
  const char *msg;
  size_t msg_offset;
  Json_dom *left_dom= Json_dom::parse(left_text, std::strlen(left_text),
                                      &msg, &msg_offset);
  Json_dom *right_dom= Json_dom::parse(right_text, std::strlen(right_text),
                                       &msg, &msg_offset);
  Json_dom *result_dom= merge_doms(left_dom, right_dom);

  EXPECT_EQ(expected, format(*result_dom));

  delete result_dom;
}

TEST_F(JsonDomTest, MergeTest)
{
  // merge 2 scalars
  {
    SCOPED_TRACE("");
    vet_merge((char *) "1", (char *) "true", "[1, true]");
  }

  // merge a scalar with an array
  {
    SCOPED_TRACE("");
    vet_merge((char *) "1", (char *) "[true, false]", "[1, true, false]");
  }

  // merge an array with a scalar
  {
    SCOPED_TRACE("");
    vet_merge((char *) "[true, false]", (char *) "1", "[true, false, 1]");
  }

  // merge a scalar with an object
  {
    SCOPED_TRACE("");
    vet_merge((char *) "1", (char *) "{\"a\": 2}", "[1, {\"a\": 2}]");
  }

  // merge an object with a scalar
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 2}", (char *) "1", "[{\"a\": 2}, 1]");
  }

  // merge 2 arrays
  {
    SCOPED_TRACE("");
    vet_merge((char *) "[1, 2]", (char *) "[3, 4]", "[1, 2, 3, 4]");
  }

  // merge 2 objects
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": 2 }",
              (char *) "{\"c\": 3, \"d\": 4 }",
              "{\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}");
  }

  // merge an array with an object
  {
    SCOPED_TRACE("");
    vet_merge((char *) "[1, 2]",
              (char *) "{\"c\": 3, \"d\": 4 }",
              "[1, 2, {\"c\": 3, \"d\": 4}]");
  }

  // merge an object with an array
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"c\": 3, \"d\": 4 }",
              (char *) "[1, 2]",
              "[{\"c\": 3, \"d\": 4}, 1, 2]");
  }

  // merge two objects which share a key. scalar + scalar
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": 2 }",
              (char *) "{\"b\": 3, \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, 3], \"d\": 4}");
  }

  // merge two objects which share a key. scalar + array
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": 2 }",
              (char *) "{\"b\": [3, 4], \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, 3, 4], \"d\": 4}");
  }

  // merge two objects which share a key. array + scalar
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": [2, 3] }",
              (char *) "{\"b\": 4, \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, 3, 4], \"d\": 4}");
  }

  // merge two objects which share a key. scalar + object
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": 2 }",
              (char *) "{\"b\": {\"e\": 7, \"f\": 8}, \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, {\"e\": 7, \"f\": 8}], \"d\": 4}");
  }

  // merge two objects which share a key. object + scalar
  {
    SCOPED_TRACE("");
    vet_merge((char *) (char *) "{\"b\": {\"e\": 7, \"f\": 8}, \"d\": 4 }",
              (char *) "{\"a\": 1, \"b\": 2 }",
              "{\"a\": 1, \"b\": [{\"e\": 7, \"f\": 8}, 2], \"d\": 4}");
  }

  // merge two objects which share a key. array + array
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": [2, 9] }",
              (char *) "{\"b\": [10, 11], \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, 9, 10, 11], \"d\": 4}");
  }

  // merge two objects which share a key. array + object
  {
    SCOPED_TRACE("");
    vet_merge((char *) "{\"a\": 1, \"b\": [2, 9] }",
              (char *) "{\"b\": {\"e\": 7, \"f\": 8}, \"d\": 4 }",
              "{\"a\": 1, \"b\": [2, 9, {\"e\": 7, \"f\": 8}], \"d\": 4}");
  }

  // merge two objects which share a key. object + array
  {
    SCOPED_TRACE("");
    vet_merge((char *) (char *) "{\"b\": {\"e\": 7, \"f\": 8}, \"d\": 4 }",
              (char *) "{\"a\": 1, \"b\": [2, 9] }",
              "{\"a\": 1, \"b\": [{\"e\": 7, \"f\": 8}, 2, 9], \"d\": 4}");
  }

  // merge two objects which share a key. object + object
  {
    SCOPED_TRACE("");
    vet_merge((char *) (char *) "{\"b\": {\"e\": 7, \"f\": 8}, \"d\": 4 }",
              (char *) "{\"a\": 1, \"b\": {\"e\": 20, \"g\": 21 } }",
              "{\"a\": 1, \"b\": {\"e\": [7, 20], \"f\": 8, \"g\": 21}, "
              "\"d\": 4}");
  }
}

}  // namespace
